//
// Created by antler on 2018/11/6.
//

#include "LuaJavaHelper.h"

#include <pthread.h>

#include "GlobalMacros.h"
#include "JNIDef.h"
#include "utils/StringUtils.h"
#include "utils/FileUtils.h"

#include "xxtea.h"
#include "unzip.h"

#include "luaext/screen/Screen.h"
#include "luaext/xmod/XMod.h"
#include "luaext/ui/UI.h"
#include "luaext/script/Script.h"

void _detachCurrentThread(void *value) {
    LuaJavaHelper::getInstance()->detachCurrentThread(value);
}

void LuaJavaHelper::detachCurrentThread(void *value) {
    tlsThreadInfo *tInfo = static_cast<tlsThreadInfo *>(value);

    if (tInfo) {
        getJavaVM()->DetachCurrentThread();
    }

    SAFE_DELETE(tInfo);
}

LuaJavaHelper *LuaJavaHelper::g_luaJavaHelper = nullptr;

LuaJavaHelper *LuaJavaHelper::getInstance() {
    if (!g_luaJavaHelper) {
        g_luaJavaHelper = new(std::nothrow)LuaJavaHelper();
    }
    return g_luaJavaHelper;
}

void LuaJavaHelper::releaseInstance() {
    SAFE_DELETE(g_luaJavaHelper);
}

LuaJavaHelper::LuaJavaHelper()
        : _pJavaVM(nullptr), _jobjCallback(nullptr), _luaState(nullptr), _stop(false),
          _xxteaEnabled(false), _xxteaKey(nullptr), _xxteaKeyLen(0), _xxteaSign(nullptr),
          _xxteaSignLen(0), _zipFile(nullptr) {
}

LuaJavaHelper::~LuaJavaHelper() {
}

void LuaJavaHelper::onLoad(JavaVM *javaVM) {
    setJavaVM(javaVM);

    pthread_key_create(&_key, _detachCurrentThread);
    pthread_cond_init(&_cond, NULL);
    pthread_mutex_init(&_mutex, NULL);
}

void LuaJavaHelper::onUnload(JavaVM *javaVM) {
    pthread_key_delete(_key);
    pthread_cond_destroy(&_cond);
    pthread_mutex_destroy(&_mutex);
}

void LuaJavaHelper::openLibs() {
    lua_State *L = getLuaState();

    luaL_openlibs(L);
    luaL_openextlibs(L);
    toluahelper_open(L);
    luaopen_cjson(L);
    luaopen_cjson_safe(L);
    luaopen_lua_extensions(L);
    addAssetsLoader(L);

    XMod::getInstance();
    Screen::getInstance();
    UI::getInstance();
    Script::getInstance();
}

void LuaJavaHelper::closeLibs() {
    Script::releaseInstance();
    UI::releaseInstance();
    Screen::releaseInstance();
    XMod::releaseInstance();

    cleanupXXTEAKeyAndSign();

    SAFE_DELETE(_zipFile);

    closeLuaState();
}

void LuaJavaHelper::setPackagePath(JNIEnv *env, jstring jpath) {
    lua_State *L = getLuaState();

    std::string path = jstring2string(env, jpath);
    path = ";" + path;

    lua_getglobal(L, "package"); // Stack: package
    lua_getfield(L, -1, "path");
    lua_pushstring(L, path.c_str());
    lua_concat(L, 2);
    lua_setfield(L, -2, "path");
}

void LuaJavaHelper::setPackageCPath(JNIEnv *env, jstring jcpath) {
    lua_State *L = getLuaState();

    std::string cpath = jstring2string(env, jcpath);
    cpath = ";" + cpath;

    lua_getglobal(L, "package"); // Stack: package
    lua_getfield(L, -1, "cpath");
    lua_pushstring(L, cpath.c_str());
    lua_concat(L, 2);
    lua_setfield(L, -2, "cpath");
}

void LuaJavaHelper::setJavaVM(JavaVM *javaVM) {
    _pJavaVM = javaVM;
}

JavaVM *LuaJavaHelper::getJavaVM() {
    return _pJavaVM;
}

JNIEnv *LuaJavaHelper::getEnv() {
    JNIEnv *env = getEnvFromTls();
    if (!env) {
        env = cacheEnv(_pJavaVM);
    }
    return env;
}

JNIEnv *LuaJavaHelper::getEnvFromTls() {
    tlsThreadInfo *tInfo = getTlsThreadInfo(true);
    if (!tInfo) {
        return nullptr;
    }

    return tInfo->env;
}

void LuaJavaHelper::setEnvToTls(JNIEnv *env) {
    tlsThreadInfo *tInfo = getTlsThreadInfo(true);
    if (tInfo) {
        tInfo->env = env;
    }
}

JNIEnv *LuaJavaHelper::cacheEnv(JavaVM *javaVM) {
    JNIEnv *env = nullptr;

    jint ret = javaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
    switch (ret) {
        case JNI_OK:
            setEnvToTls(env);
            break;
        case JNI_EDETACHED:
            if (javaVM->AttachCurrentThread(&env, nullptr) < 0) {
                LOGE("Failed to get the environment using AttachCurrentThread()");
            } else {
                setEnvToTls(env);
            }
            break;
        case JNI_EVERSION:
            LOGE("JNI interface version 1.4 not supported");
        default:
            LOGE("Failed to get the environment using GetEnv()");
            break;
    }

    return env;
}

lua_State *LuaJavaHelper::getLuaState() {
    lua_State *L = getLuaStateFromTls();
    if (!L) {
        L = cacheLuaState();
    }
    return L;
}

lua_State *LuaJavaHelper::getLuaStateFromTls() {
    return _luaState;
}

void LuaJavaHelper::setLuaStateToTls(lua_State *L) {
    _luaState = L;
}

lua_State *LuaJavaHelper::cacheLuaState() {
    lua_State *L = nullptr;

    L = luaL_newstate();
    if (L) {
        setLuaStateToTls(L);
    }

    return L;
}

void LuaJavaHelper::closeLuaState() {
    if (_luaState) {
        lua_close(_luaState);
        _luaState = nullptr;
    }
}

LuaJavaHelper::tlsThreadInfo *LuaJavaHelper::getTlsThreadInfo(bool create) {
    tlsThreadInfo *tInfo = (tlsThreadInfo *) pthread_getspecific(_key);
    if (!tInfo && create) {
        tInfo = new(std::nothrow)tlsThreadInfo();
        if (tInfo) {
            pthread_setspecific(_key, tInfo);
        }
    }
    return tInfo;
}

std::string LuaJavaHelper::jstring2string(JNIEnv *env, jstring jstr) {
    if (!env) {
        return "";
    }

    if (jstr == nullptr) {
        return "";
    }

    std::string strValue;

    const char *utf = env->GetStringUTFChars(jstr, nullptr);
    if (utf) {
        strValue = utf;
        env->ReleaseStringUTFChars(jstr, utf);
    }

    return strValue;
}

jobject LuaJavaHelper::getCallbackObj() {
    return _jobjCallback;
}

void LuaJavaHelper::setCallbackObj(jobject jobj) {
    JNIEnv *env = getEnv();
    if (!env) {
        return;
    }

    if (_jobjCallback) {
        env->DeleteGlobalRef(_jobjCallback);
    }

    _jobjCallback = env->NewGlobalRef(jobj);
}

void
LuaJavaHelper::callVoidMethod(JNIEnv *env, jobject jobj, const char *methodName, const char *sig,
                              ...) {
    if (!env) {
        return;
    }

    if (!jobj) {
        LOGE("callVoidMethod jobj is null")
        return;
    }

    jclass jcls = env->GetObjectClass(jobj);
    if (jcls) {
        jmethodID jmethodID = env->GetMethodID(jcls, methodName, sig);
        if (jmethodID) {
            va_list args;
            va_start(args, sig);
            env->CallVoidMethodV(jobj, jmethodID, args);
            va_end(args);
        } else {
            LOGV("callVoidMethod %s: %s not found", methodName, sig);
        }

        env->DeleteLocalRef(jcls);
    }

    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
}

jobject
LuaJavaHelper::callObjectMethod(JNIEnv *env, jobject jobj, const char *methodName, const char *sig,
                                ...) {
    if (!env) {
        return nullptr;
    }

    if (!jobj) {
        LOGE("callStringMethod jobj is null")
        return nullptr;
    }

    jobject result = nullptr;

    jclass jcls = env->GetObjectClass(jobj);
    if (jcls) {
        jmethodID jmethodID = env->GetMethodID(jcls, methodName, sig);
        if (jmethodID) {
            va_list args;
            va_start(args, sig);
            result = env->CallObjectMethod(jobj, jmethodID, args);
            va_end(args);
        } else {
            LOGV("callObjectMethod %s: %s not found", methodName, sig);
        }

        env->DeleteLocalRef(jcls);
    }

    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }

    return result;
}

void LuaJavaHelper::callStaticVoidMethod(JNIEnv *env, const char *className, const char *methodName,
                                         const char *sig, ...) {
    if (!env) {
        return;
    }

    jclass jcls = env->FindClass(className);
    if (jcls) {
        jmethodID jmethodID = env->GetStaticMethodID(jcls, methodName, sig);
        if (jmethodID) {
            va_list args;
            va_start(args, sig);
            env->CallStaticVoidMethodV(jcls, jmethodID, args);
            va_end(args);
        } else {
            LOGV("callStaticVoidMethod %s: %s: %s not found", className, methodName, sig);
        }
        env->DeleteLocalRef(jcls);
    }

    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
}

jobject
LuaJavaHelper::callStaticObjectMethod(JNIEnv *env, const char *className, const char *methodName,
                                      const char *sig, ...) {
    if (!env) {
        return nullptr;
    }

    jobject result = nullptr;

    jclass jcls = env->FindClass(className);
    if (jcls) {
        jmethodID jmethodID = env->GetStaticMethodID(jcls, methodName, sig);
        if (jmethodID) {
            va_list args;
            va_start(args, sig);
            result = env->CallStaticObjectMethodV(jcls, jmethodID, args);
            va_end(args);
        } else {
            LOGV("callStaticObjectMethod %s: %s: %s not found", className, methodName, sig);
        }
        env->DeleteLocalRef(jcls);
    }

    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }

    return result;
}

jboolean
LuaJavaHelper::callStaticBooleanMethod(JNIEnv *env, const char *className, const char *methodName,
                                       const char *sig, ...) {
    if (!env) {
        return JNI_FALSE;
    }

    jboolean result = JNI_FALSE;

    jclass jcls = env->FindClass(className);
    if (jcls) {
        jmethodID jmethodID = env->GetStaticMethodID(jcls, methodName, sig);
        if (jmethodID) {
            va_list args;
            va_start(args, sig);
            result = env->CallStaticBooleanMethodV(jcls, jmethodID, args);
            va_end(args);
        } else {
            LOGV("callStaticBooleanMethod %s: %s: %s not found", className, methodName, sig);
        }
        env->DeleteLocalRef(jcls);
    }

    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }

    return result;
}

jint
LuaJavaHelper::callStaticIntMethod(JNIEnv *env, const char *className, const char *methodName,
                                   const char *sig, ...) {
    if (!env) {
        return -1;
    }

    jint result = -1;

    jclass jcls = env->FindClass(className);
    if (jcls) {
        jmethodID jmethodID = env->GetStaticMethodID(jcls, methodName, sig);
        if (jmethodID) {
            va_list args;
            va_start(args, sig);
            result = env->CallStaticIntMethodV(jcls, jmethodID, args);
            va_end(args);
        } else {
            LOGV("callStaticIntMethod %s: %s: %s not found", className, methodName, sig);
        }
        env->DeleteLocalRef(jcls);
    }

    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }

    return result;
}

int
LuaJavaHelper::runShellCommand(JNIEnv *env, const char *command, bool isRoot, std::string *pResult,
                               int timeoutMS) {
    if (!env) {
        return -1;
    }

    int ret = -1;

    ScopedLocalRef<jstring> jcommand(env, env->NewStringUTF(command));
    if (jcommand) {
        jobject jresult_ = callStaticObjectMethod(env, CLASS_NAME_SHELLUTILS,
                                                  METHOD_NAME_SHELLUTILS_EXECCOMMAND,
                                                  METHOD_SIG_SHELLUTILS_EXECCOMMAND,
                                                  (jstring) jcommand,
                                                  isRoot,
                                                  pResult != nullptr,
                                                  timeoutMS);
        ScopedLocalRef<jobject> jresult(env, jresult_);
        if (jresult) {
            jclass jclazz = env->GetObjectClass(jresult);

            jfieldID jfieldResult = env->GetFieldID(jclazz,
                                                    FIELD_NAME_COMMANDRESULT_RESULT, "I");
            jfieldID jfieldSuccessMsg = env->GetFieldID(jclazz,
                                                        FIELD_NAME_COMMANDRESULT_SUCCESSMSG,
                                                        "Ljava/lang/String;");
            jfieldID jfieldErrorMsg = env->GetFieldID(jclazz,
                                                      FIELD_NAME_COMMANDRESULT_ERRORMSG,
                                                      "Ljava/lang/String;");
            jint result = env->GetIntField(jresult, jfieldResult);
            jstring successMsg = (jstring) env->GetObjectField(jresult, jfieldSuccessMsg);
            jstring errorMsg = (jstring) env->GetObjectField(jresult, jfieldErrorMsg);

            ret = (int) result;
            if (pResult) {
                *pResult = jstring2string(env, successMsg);
            }
        }
    }

    return ret;
}

bool LuaJavaHelper::isStopFlag() {
    return _stop;
}

void LuaJavaHelper::setStopFlag(bool value) {
    _stop = value;
}

void LuaJavaHelper::stop() {
    setStopFlag(true);

    pthread_mutex_lock(&_mutex);
    pthread_cond_signal(&_cond);
    pthread_mutex_unlock(&_mutex);
}

void LuaJavaHelper::sleep(long timeoutMS) {
    if (timeoutMS <= 0) {
        return;
    }

    struct timespec now;
    clock_gettime(CLOCK_REALTIME, &now);

    struct timespec timeout = {timeoutMS / 1000, (timeoutMS % 1000) * 1000000};

    struct timespec abstime;
    abstime.tv_sec = now.tv_sec + timeout.tv_sec;
    abstime.tv_nsec = now.tv_nsec + timeout.tv_nsec;
    abstime.tv_sec += abstime.tv_nsec / 1000000000;
    abstime.tv_nsec = abstime.tv_nsec % 1000000000;

    pthread_mutex_lock(&_mutex);
    pthread_cond_timedwait(&_cond, &_mutex, &abstime);
    pthread_mutex_unlock(&_mutex);
}

static void lstop(lua_State *L, lua_Debug *ar) {
    if (LuaJavaHelper::getInstance()->isStopFlag()) {
        luaL_error(L, "interrupted!");
    }
}

jint LuaJavaHelper::doFile(JNIEnv *env, jobject jobj, jstring jfilepath) {
    lua_State *L = getLuaState();
    if (!L) {
        return LUA_ERRRUN;
    }

    setStopFlag(false);

    lua_sethook(L, lstop, LUA_MASKCOUNT, 1);

    int ret = LUA_OK;
    std::string filepath = jstring2string(env, jfilepath);
    if (StringUtils::endWith(filepath, ZIP_FILE_EXT) ||
        StringUtils::endWith(filepath, XSP_FILE_EXT)) {
        ret = doZipfile(L, filepath.c_str());
    } else {
        ret = luaL_dofile(L, filepath.c_str());
    }

    lua_sethook(L, NULL, 0, 0);

    if (ret == LUA_OK) {
        lua_getglobal(L, "debug");
        lua_getfield(L, -1, "traceback");
        ret = lua_pcall(L, 0, 0, -1);
        if (ret == LUA_OK) {
            return ret;
        }
    }

    const char *trackback = lua_tostring(L, -1);

    jclass clsException = env->FindClass(CLASS_NAME_EXCEPTION);
    if (clsException != nullptr) {
        env->ThrowNew(clsException, trackback
        );
        env->DeleteLocalRef(clsException);
    }
    return LUA_ERRRUN;
}

jint LuaJavaHelper::doString(JNIEnv *env, jobject jobj, jstring jstr) {
    lua_State *L = getLuaState();
    if (!L) {
        return LUA_ERRRUN;
    }

    setStopFlag(false);

    std::string str = jstring2string(env, jstr);

    lua_sethook(L, lstop, LUA_MASKCOUNT, 1);
    int ret = luaL_dostring(L, str.c_str());
    lua_sethook(L, NULL, 0, 0);

    if (ret == LUA_OK) {
        lua_getglobal(L, "debug");
        lua_getfield(L, -1, "traceback");
        ret = lua_pcall(L, 0, 0, -1);
        if (ret == LUA_OK) {
            return ret;
        }
    }

    const char *trackback = lua_tostring(L, -1);

    jclass clsException = env->FindClass(CLASS_NAME_EXCEPTION);
    if (clsException != nullptr) {
        env->ThrowNew(clsException, trackback);
        env->DeleteLocalRef(clsException);
    }
    return LUA_ERRRUN;
}

void LuaJavaHelper::sendEvent(JNIEnv *env, jobject jobj, jstring jid, jint jaction, jint jfunID) {
    lua_State *L = getLuaState();
    if (!L) {
        return;
    }

    std::string id = jstring2string(env, jid);

    LOGV("sendEvent id:%s, action:%d, funID:%d", id.c_str(), jaction, jfunID);

    toluahelper_get_function_by_refid(L, jfunID);
    if (lua_isfunction(L, -1)) {
        lua_pushstring(L, id.c_str());
        lua_pushinteger(L, jaction);
        lua_pcall(L, 2, 0, 0);
    }
}

void LuaJavaHelper::removeActionCallback(JNIEnv *env, jobject jobj, jint jaction, jint jfunID) {
    lua_State *L = getLuaState();
    if (!L) {
        return;
    }

    LOGV("removeActionCallback action:%d, funID:%d", jaction, jfunID);

    toluahelper_remove_function_by_refid(L, jfunID);
}

int LuaJavaHelper::searcher_assets(lua_State *L) {
    int ret = 1;

    do {
        const char *filename = luaL_checkstring(L, 1);
        filename = luaL_gsub(L, filename, ".", LUA_DIRSEP);
        if (filename == NULL) {
            break;
        }

        JNIEnv *env = LuaJavaHelper::getInstance()->getEnv();
        if (!env) {
            break;
        }

        static const char *FILE_EXT[] = {NOT_BYTECODE_FILE_EXT_MARK, BYTECODE_FILE_EXT_MARK};
        for (int i = 0; i < sizeof(FILE_EXT) / sizeof(FILE_EXT[0]); ++i) {
            const char *newfilename = luaL_gsub(L, FILE_EXT[i], "?", filename);

            LOGV("searcher_assets filename:%s", newfilename);

            ScopedLocalRef<jstring> jfilename(env, env->NewStringUTF(newfilename));
            if (!jfilename) {
                break;
            }

            jbyteArray jbyteBuffer = (jbyteArray) callStaticObjectMethod(env,
                                                                         CLASS_NAME_COMMONUTILS,
                                                                         METHOD_NAME_COMMONUTILS_ASSETSLOADER,
                                                                         METHOD_SIG_COMMONUTILS_ASSETSLOADER,
                                                                         (jstring) jfilename);
            if (!jbyteBuffer) {
                continue;
            }

            jboolean isCopy;
            jbyte *jbyte = env->GetByteArrayElements(jbyteBuffer, &isCopy);
            if (jbyte) {
                jsize size = env->GetArrayLength(jbyteBuffer);
                if (LuaJavaHelper::getInstance()->luaLoadBuffer(L, (const char *) jbyte, size,
                                                                newfilename) == LUA_OK) {
                    lua_pushstring(L, newfilename);
                    ret = 2;
                }
            }
            env->ReleaseByteArrayElements(jbyteBuffer, jbyte, isCopy);

            break;
        }

    } while (0);

    return ret;
}

void LuaJavaHelper::addAssetsLoader(lua_State *L) {
    lua_getglobal(L, "package");
    lua_getfield(L, -1, "searchers");
    size_t nLoaders = lua_rawlen(L, -1);
    lua_pushcfunction(L, LuaJavaHelper::searcher_assets);  // Stack: package searcher_assets
    lua_rawseti(L, -2, nLoaders + 1);
    lua_pop(L, 1);
}

void LuaJavaHelper::setXXTEAKeyAndSign(const char *key, int keyLen, const char *sign, int signLen) {
    cleanupXXTEAKeyAndSign();

    if (key && keyLen && sign && signLen) {
        _xxteaKey = (char *) malloc(keyLen);
        memcpy(_xxteaKey, key, keyLen);
        _xxteaKeyLen = keyLen;

        _xxteaSign = (char *) malloc(signLen);
        memcpy(_xxteaSign, sign, signLen);
        _xxteaSignLen = signLen;

        _xxteaEnabled = true;
    } else {
        _xxteaEnabled = false;
    }
}

void LuaJavaHelper::cleanupXXTEAKeyAndSign() {
    if (_xxteaKey) {
        SAFE_FREE(_xxteaKey);
        _xxteaKeyLen = 0;
    }

    if (_xxteaSign) {
        SAFE_FREE(_xxteaSign);
        _xxteaSignLen = 0;
    }
}

int LuaJavaHelper::doZipfile(lua_State *L, const char *zipFilePath) {
    int ret = LUA_OK;

    const char *key = "12345";
    const char *sign = "54321";

    setXXTEAKeyAndSign(key, strlen(key), sign, strlen(sign));

    do {
        ResizableBuffer buf;
        if (!FileUtils::getFileData(zipFilePath, buf)) {
            break;
        }

        ZipFile *zip = nullptr;
        unsigned char *buffer = nullptr;
        unsigned char *bytes = buf.data();
        size_t size = buf.size();

        bool isXXTEA = _xxteaEnabled && size >= _xxteaSignLen &&
                       memcmp(_xxteaSign, bytes, _xxteaSignLen) == 0;
        if (isXXTEA) {
            xxtea_long len = 0;
            buffer = xxtea_decrypt(bytes + _xxteaSignLen,
                                   (xxtea_long) size - (xxtea_long) _xxteaSignLen,
                                   (unsigned char *) _xxteaKey,
                                   (xxtea_long) _xxteaKeyLen, &len);
            bytes = buffer;
            size = len;
        }

        if (size <= 0) {
            break;
        }

        const char XSP_SIGN[] = {'x', 's', 'p', '\x0', '\x30', '\x30', '\x30', '\x0'};
        const int XSP_LEN = sizeof(XSP_SIGN) / sizeof(XSP_SIGN[0]);
        if (size >= XSP_LEN && memcmp(XSP_SIGN, bytes, XSP_LEN) == 0) {
            bytes += XSP_LEN;
            size -= XSP_LEN;
        }

        zip = ZipFile::createWithBuffer(bytes, size);
        SAFE_FREE(buffer);

        if (!zip) {
            break;
        }

        ResizableBuffer manifestBuf;
        if (!zip->getFileData(MANIFEST_JSON, manifestBuf)) {
            LOGE("[doZipfile] not found 'manifest.json'");
            break;
        }

        std::string main;
        std::string bin_sha1;
        int tengine = 0;
        std::string version;

        std::string mainifestJson((const char *) manifestBuf.data(), manifestBuf.size());
        toluahelper_json2table(L, mainifestJson);
        if (lua_type(L, -1) == LUA_TTABLE) {
            lua_getfield(L, -1, MANIFEST_JSON_KEY_MAIN);
            main = tolua_tocppstring(L, -1, "");
            lua_pop(L, 1);

            lua_getfield(L, -1, MANIFEST_JSON_KEY_BIN_SHA1);
            bin_sha1 = tolua_tocppstring(L, -1, "");
            lua_pop(L, 1);

            lua_getfield(L, -1, MANIFEST_JSON_KEY_TENGINE);
            tengine = tolua_tonumber(L, -1, 0);
            lua_pop(L, 1);

            lua_getfield(L, -1, MANIFEST_JSON_KEY_VERSION);
            version = tolua_tocppstring(L, -1, "");
            lua_pop(L, 1);
        }
        lua_pop(L, 1);

        if (main.empty()) {
            LOGE("[doZipfile] not found main entry");
            break;
        }

        main = StringUtils::removeExtension(main);

        LOGV("[doZipfile] found main entry: %s", main.c_str());

        lua_getglobal(L, "package");
        lua_getfield(L, -1, "preload");

        int count = 0;

        FileListContainer fileList;
        zip->getFileList(fileList);
        for (const auto &file : fileList) {
            std::string filename = file.first;

            ResizableBuffer buf;
            if (zip->getFileData(filename, buf)) {
                size_t pos = filename.find_last_of('.');
                if (pos != std::string::npos) {
                    std::string suffix = filename.substr(pos);
                    if (suffix == NOT_BYTECODE_FILE_EXT || suffix == BYTECODE_FILE_EXT) {
                        filename.erase(pos);

                        for (auto &c : filename) {
                            if (c == '/' || c == '\\') {
                                c = '.';
                            }
                        }

                        if (luaLoadBuffer(L, (const char *) buf.data(), (int) buf.size(),
                                          filename.c_str()) == LUA_OK) {
                            lua_setfield(L, -2, filename.c_str());

                            LOGV("[doZipfile] add %s to preload ok", filename.c_str());

                            ++count;
                        } else {
                            LOGV("[doZipfile] add %s to preload err", filename.c_str());
                        }
                    }
                }
            }
        }

        lua_pop(L, 2);

        LOGV("[doZipfile] loaded chunks count: %d", count);

        // execute lua
        std::string require = "require('" + main + "')";
        ret = luaL_dostring(L, require.c_str());

    } while (0);
    return ret;
}

namespace {
    void skipBOM(const char *&chunk, int &chunkSize) {
        // UTF-8 BOM? skip
        if (static_cast<unsigned char>(chunk[0]) == 0xEF &&
            static_cast<unsigned char>(chunk[1]) == 0xBB &&
            static_cast<unsigned char>(chunk[2]) == 0xBF) {
            chunk += 3;
            chunkSize -= 3;
        }
    }
} // end anonymous namespace

int LuaJavaHelper::luaLoadBuffer(lua_State *L, const char *chunk, int chunkSize,
                                 const char *chunkName) {
    int ret = LUA_ERRERR;

    do {
        if (!chunk || !chunkSize || !chunkName) {
            break;
        }

        bool isXXTEA = _xxteaEnabled && chunkSize >= _xxteaSignLen &&
                       memcmp(_xxteaSign, chunk, _xxteaSignLen) == 0;
        if (isXXTEA) {
            xxtea_long len = 0;
            void *buffer = (void *) xxtea_decrypt((unsigned char *) chunk + _xxteaSignLen,
                                                  (xxtea_long) chunkSize -
                                                  (xxtea_long) _xxteaSignLen,
                                                  (unsigned char *) _xxteaKey,
                                                  (xxtea_long) _xxteaKeyLen, &len);
            if (buffer) {
                const char *content = (const char *) buffer;
                int contentSize = (int) len;
                skipBOM(content, contentSize);
                ret = luaL_loadbuffer(L, content, contentSize, chunkName);
                SAFE_FREE(buffer);
            }
        } else {
            skipBOM(chunk, chunkSize);
            ret = luaL_loadbuffer(L, chunk, chunkSize, chunkName);
        }

    } while (false);
    return ret;
}

